dragできるcomponentをasync-await loopで実現
してみる
code:useDrag.ts
import {
useRef, useEffect,
} from "./preact.ts";
import {
usePromiseSettledAnytimes,
} from "./usePromiseSettledAnytimes.ts";
const handleDragStart = () => false;
export function useDrag<T extends HTMLElement = HTMLElement>() {
const ref = useRef<T>(null);
ドラッグ処理
座標計算は↓をそのまま使った
loop案その1
code:ts
while(true) {
const {clientX, clientY} = await waitDown();
const shiftX = clientX - ref.current!.getBoundingClientRect().left;
const shiftY = clientY - ref.current!.getBoundingClientRect().top;
const handleMove = ({pageX, pageY}: PointerEvent) => {
ref.current!.style.left = ${pageX - shiftX}px;
ref.current!.style.top = ${pageY - shiftY}px;
}
document.addEventListener("pointermove", handleMove);
// ↓const waitUp, handleUp = usePromiseSettledAnytimes<PointerEvent>();を定義している前提 await waitUp();
document.removeEventListener("pointermove", handleMove);
}
pointermoveも同期的に処理したかったが、うまくいかなかった
後でまた考える
10:17:58 直してみた↓
loop案その2
全部無限ループで書き換えられた!
ループが深くなるのが欠点かも
浅くしたければループ案その1を採用する
code:useDrag.ts
useEffect(() => {
document.addEventListener("pointermove", handleMove);
(async () => {
while (true) {
const {clientX, clientY} = await waitDown();
const shiftX = clientX - ref.current!.getBoundingClientRect().left;
const shiftY = clientY - ref.current!.getBoundingClientRect().top;
while (true) {
try {
const {pageX, pageY} = await waitMove();
ref.current!.style.left = ${pageX - shiftX}px;
ref.current!.style.top = ${pageY - shiftY}px;
} catch(e) {
handleUp()が呼び出されると、PointerEventが例外として投げられる
これを合図にループから抜ける
それ以外のエラーはマジもんのエラーなのでちゃんとre-throwする
code:useDrag.ts
if (e instanceof PointerEvent) break;
throw e;
}
}
}
})();
return () => document.removeEventListener("pointermove", handleMove);
code:useDrag.ts
}, []);
return {ref, handleDown, handleUp, handleDragStart};
}
例外使わなくてもループから抜けられるんじゃないか?
handleMoveをpointerup event発火時にも呼び出すようにして、ループ内でどちらのeventかを判別すればいい
code:ts
while (true) {
const {type, clientX, clientY} = await waitMove();
if (type === "pointerup") break;
// ...
}
code:app.tsx
/// <reference no-default-lib="true" />
/// <reference lib="esnext" />
/// <reference lib="dom" />
/** @jsx h */
/** @jsxFrag Fragment */
import {
h, render, Fragment,
} from "./preact.ts";
import {
useDrag
} from "./useDrag.ts";
type Props = {
onClose: () => void;
};
const App = ({onClose}: Props) => {
const {
ref,
handleDown,
handleUp,
handleDragStart,
} = useDrag<HTMLDivElement>();
return <>
<style>{`
div {
position: absolute;
top: 10px;
left: 10px;
width: 10vw;
height: 10vh;
z-index: 9999;
background-color: var(--page-bg);
border: solid 1px var(--telomere-unread);
border-radius: 4px;
}
`}</style>
<div ref={ref}
onPointerDown={handleDown}
onPointerUp={handleUp}
onDragStart={handleDragStart} />
</>
};
export function setup() {
const app = document.createElement("div");
const shadowRoot = app.attachShadow({mode: "open"});
document.body.append(app);
render(<App onClose={() => app.remove()} />, shadowRoot);
}
仕方ないので、deno bundle -r https://scrapbox.io/api/code/takker//dragできるcomponentをasync-await_loopで実現/index.ts | esbuild --minify --charset=utf8 | xselでbuildしたものを適当なコードブロックに貼り付けて実行した
うまく動いてくれた
2022-01-06 03:00:48 相対パスで動くように直したので元に戻した
スマホだとうまくドラッグされない……
code:index.ts
import {setup} from "./app.tsx";
setup();
code:usePromiseSettledAnytimes.ts
import {
useCallback, useRef,
} from "./preact.ts";
function useRefFn<T extends unknown[], U>() {
type Fn = (...args: T) => U;
const ref = useRef<Fn>();
const fn = useCallback((...args: T) => {
ref.current?.(...args);
}, []);
const setFn = useCallback((fn: Fn) => ref.current = fn, []);
}
export function usePromiseSettledAnytimes<T, E = unknown>() {
const waitForSettled = useCallback(() => new Promise<T>(
(res, rej) => {
setResolve(res);
setReject(rej);
},
), []);
}
dependencies
code:preact.ts
export {h, render, Fragment} from "../preact@10.5.14/mod.js";
export {useRef, useCallback, useEffect} from "../preact@10.5.14/hooks.js";